Skip to main content

TurboSign API Integration

This comprehensive guide walks you through the TurboSign single-step API integration. Learn how to programmatically upload documents, configure recipients, set up signature fields, and send documents for electronic signatures using a single, streamlined API call.

Agent Skill

Let an agent scaffold this for you

Install the TurboDocx Quickstart Skill and let Claude Code, Cursor, Copilot, Codex, or any agent that speaks the Agent Skills standard install the SDK, wire it into your app, and write a working TurboSign integration end-to-end.

bash — turbodocx
$npx skills add TurboDocx/quickstart
# then, inside your agent:/turbodocx-sdk turbosign

TurboSign API Integration Overview

Overview

The TurboSign API provides a simplified single-step process to prepare documents for electronic signatures. Instead of multiple API calls, you can now accomplish everything in one request.

Two Endpoint Options

TurboSign offers two single-step endpoints to fit different workflows:

  1. Prepare for Review - Upload and get preview URL (no emails sent)
  2. Prepare for Signing - Upload and send immediately (emails sent)

TurboSign Single-Step Workflow

Key Features

  • Single API Call: Upload document, add recipients, and configure fields in one request
  • RESTful API: Standard HTTP methods with multipart/form-data
  • Bearer Token Authentication: Secure API access using JWT tokens
  • Multiple Recipients: Support for multiple signers with custom signing order
  • Flexible Field Placement: Position signature fields using anchors or coordinates
  • Multiple File Sources: Upload file, or reference deliverableId, templateId, or fileLink
  • Real-time Status Updates: Track document status throughout the signing process
  • Webhook Integration: Receive notifications when signing is complete
Prefer using an SDK?

We offer official SDKs that handle authentication, error handling, and type safety for you.

View all SDKs →

TLDR; Complete Working Example 🚀

Don't want to read all the details? Here's what you need to know:

Available Field Types

TypeDescriptionUse Case
signatureElectronic signature fieldLegal signatures
initialInitial fieldDocument initials, paragraph acknowledgments
dateDate picker fieldSigning date, agreement date
full_nameFull name fieldAutomatically fills signer's complete name
first_nameFirst name fieldAutomatically fills signer's first name
last_nameLast name fieldAutomatically fills signer's last name
titleTitle/job title fieldProfessional title or position
companyCompany name fieldOrganization or company name
emailEmail address fieldSigner's email address
textGeneric text input fieldCustom text, notes, or any other text input
checkboxCheckbox fieldAcknowledgments, consent, agreements

Quick Start: Prepare for Signing (Most Common)

Use this endpoint to send documents immediately for signing:

Loading code examples...

Alternative: Prepare for Review

Use this endpoint when you need a preview URL to verify field placement:

Loading code examples...

Quick Comparison

Featureprepare-for-reviewprepare-for-signing
Sends emails?❌ No✅ Yes
Returns preview URL?✅ Yes❌ No
Returns recipients?✅ Yes✅ Yes
Final statusREVIEW_READYUNDER_REVIEW
Use whenNeed to verify field placementReady to send immediately

Now that you've seen the whole thing, let's dive into the details...

Prerequisites

Before you begin, ensure you have:

  • API Access Token: Bearer token for authentication
  • Organization ID: Your organization identifier
  • PDF Document: Document ready for signature collection

Getting Your Credentials

  1. Login to TurboDocx: Visit https://www.turbodocx.com
  2. Navigate to Settings: Access your organization settings
  3. API Keys Section: Generate or retrieve your API access token
  4. Organization ID: Copy your organization ID from the settings

TurboSign API Key TurboSign Organization ID

Authentication

All TurboSign API requests require authentication using a Bearer token in the Authorization header:

Authorization: Bearer YOUR_API_TOKEN

Additional required headers for all requests:

x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Choosing Your Endpoint

TurboSign offers two single-step endpoints to fit different workflows. Choose the one that best matches your use case.

When to Use prepare-for-review

Use this endpoint when you want to:

  • Verify field placement before sending to recipients
  • Get a preview URL to review the document in TurboSign's interface
  • Manually trigger email sending after verifying field placement
  • Ensure correct field positioning before recipients receive emails

Workflow: Upload → Get preview URL → Review in browser → Manually send when ready

When to Use prepare-for-signing

Use this endpoint when you want to:

  • Send documents immediately without preview step
  • Automate the entire signature process end-to-end
  • Use with verified templates or confident field positioning
  • Skip manual review and send directly to recipients

Workflow: Upload → Emails sent automatically → Recipients sign

Endpoint 1: Prepare for Review

Creates a signature request and returns a preview URL. No emails are sent to recipients.

Endpoint

POST https://api.turbodocx.com/turbosign/single/prepare-for-review

Headers

Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Request Body (multipart/form-data)

⚠️ Important: Recipients and fields must be sent as JSON strings in form-data

FieldTypeRequiredDescription
fileFileConditional*PDF, DOCX, or PPTX file to upload
deliverableIdString (UUID)Conditional*Reference to existing deliverable
templateIdString (UUID)Conditional*Reference to existing template
fileLinkString (URL)Conditional*URL to download file from
documentNameStringNoDocument name in TurboSign (max 255 chars)
documentDescriptionStringNoDocument description (max 1000 chars)
recipientsString (JSON)YesJSON string array of recipient objects
fieldsString (JSON)YesJSON string array of field objects
senderNameStringNoName of sender (max 255 chars). Defaults to your API key's name.
senderEmailString (email)YesReply-to address on the signature email and the sender recorded in the audit trail
ccEmailsString (JSON)NoJSON string array of CC email addresses
remindersEnabledBooleanNoSend reminder emails to signers who haven't signed
reminderDelayString (JSON)NoTime to the FIRST reminder, {"value":3,"unit":"days"}
reminderIntervalString (JSON)NoGap between later reminders
maxRemindersNumberNoCap per signer, max 50. -1 unlimited, 0 none
expirationEnabledBooleanNoClose the signing window after expireAfter
expireAfterString (JSON)NoHow long the document stays signable
expirationWarningString (JSON)NoHow far before expiry warnings start. 0 = never warn
expirationWarningIntervalString (JSON)NoGap between warnings once they start

* File Source: Must provide exactly ONE of: file, deliverableId, templateId, or fileLink

Reminders & expiration are optional

The eight schedule fields are per-document overrides. Omit any of them and that setting is inherited from your organization's E-Signature defaults; omit all of them and the document simply follows the org policy as it stands at send time. Both reminders and expiration ship off by default, so leaving these out preserves today's behaviour exactly.

The resolved schedule is frozen onto the document when it is sent — changing your org defaults later never alters a document already out for signature.

Durations are {value, unit} objects sent as JSON strings, because multipart/form-data cannot carry a nested value:

reminderDelay={"value":3,"unit":"days"}
expireAfter={"value":30,"unit":"hours"}

unit is "hours" or "days". value is a whole number, minimum 1 and at most 999 days (23976 hours) — except expirationWarning, where 0 means "never send a warning".

See Reminders & Expiration for the full behaviour.

senderEmail is required

senderEmail must be supplied on every API/SDK signature request. A request authenticated with an API key has no mailbox of its own, so TurboDocx rejects the request with HTTP 400 and the error code SenderEmailRequired rather than sending from an unmonitored address. senderName is optional — it defaults to the name of your API key (shown in the recipient's email and the audit trail); if no name can be resolved at all the API returns 400 SenderNameRequired.

TurboQuote works differently: quotes have no senderEmail request field — the sender is resolved from the org quote template. See Prepared By & Sender Identity.

Recipients JSON Format

Recipients must be stringified before adding to form-data:

const recipients = JSON.stringify([
{
name: "John Smith",
email: "john.smith@company.com",
signingOrder: 1,
metadata: {
color: "hsl(200, 75%, 50%)",
lightColor: "hsl(200, 75%, 93%)",
},
},
{
name: "Jane Doe",
email: "jane.doe@partner.com",
signingOrder: 2,
metadata: {
color: "hsl(270, 75%, 50%)",
lightColor: "hsl(270, 75%, 93%)",
},
},
]);
formData.append("recipients", recipients);

Fields JSON Format

Fields reference recipients by email (not recipientId) and must be stringified:

const fields = JSON.stringify([
{
recipientEmail: "john.smith@company.com",
type: "signature",
template: {
anchor: "{Signature1}",
placement: "replace",
size: { width: 200, height: 80 },
offset: { x: 0, y: 0 },
},
required: true,
},
{
recipientEmail: "john.smith@company.com",
type: "date",
template: {
anchor: "{Date1}",
placement: "replace",
size: { width: 150, height: 30 },
},
required: true,
},
]);
formData.append("fields", fields);

Coordinate-based:

const fields = JSON.stringify([
{
recipientEmail: "john.smith@company.com",
type: "signature",
page: 1,
x: 100,
y: 200,
width: 200,
height: 80,
pageWidth: 612,
pageHeight: 792,
required: true,
},
]);
formData.append("fields", fields);

Response

{
"success": true,
"documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
"status": "REVIEW_READY",
"previewUrl": "https://www.turbodocx.com/sign/preview/abc123...",
"recipients": [
{
"id": "5f673f37-9912-4e72-85aa-8f3649760f6b",
"name": "John Smith",
"email": "john.smith@company.com",
"signingOrder": 1,
"metadata": {
"color": "hsl(200, 75%, 50%)",
"lightColor": "hsl(200, 75%, 93%)"
}
}
],
"message": "Document prepared for review. Use the preview URL to review and assign fields."
}

Response Fields

FieldTypeDescription
successBooleanRequest success status
documentIdString (UUID)Unique document identifier - save for tracking
statusStringDocument status (REVIEW_READY)
previewUrlString (URL)URL to preview and verify document
recipientsArrayArray of recipient objects with generated IDs
messageStringHuman-readable success message

Code Examples

Loading code examples...

Next Steps After Review

Once you've reviewed the document via the preview URL click "Send for Signing" button on the preview page to send emails to recipients

Endpoint 2: Prepare for Signing

Creates a signature request and immediately sends emails to recipients. Use this for production workflows when you're confident in your field positioning.

Endpoint

POST https://api.turbodocx.com/turbosign/single/prepare-for-signing

Headers

Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Request Body (multipart/form-data)

The request format is identical to prepare-for-review. See the "Endpoint 1: Prepare for Review" section above for detailed field documentation.

Response

{
"success": true,
"documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
"status": "UNDER_REVIEW",
"recipients": [
{
"id": "5f673f37-9912-4e72-85aa-8f3649760f6b",
"name": "John Smith",
"email": "john.smith@company.com",
"signingOrder": 1,
"metadata": {
"color": "hsl(200, 75%, 50%)",
"lightColor": "hsl(200, 75%, 93%)"
}
}
],
"message": "Document sent for signing. Emails are being sent to recipients."
}

Response Fields

FieldTypeDescription
successBooleanRequest success status
documentIdString (UUID)Unique document identifier - save for tracking
statusStringDocument status (UNDER_REVIEW)
recipientsArrayArray of recipient objects with generated IDs
messageStringHuman-readable success message

⚠️ Note: This endpoint returns immediately after creating the document. Email sending happens asynchronously in the background. Use webhooks to receive notification when the document is fully signed.

Code Examples

Loading code examples...
Need to resend emails?

If a recipient hasn't received or has lost their signing email, you can resend it using the Resend Email endpoint. You'll need the recipientIds from the response of this endpoint.

Endpoint 3: Download Signed Document

After a document has been signed by all recipients (status: COMPLETED), you can download the final signed PDF document.

Endpoint

GET https://api.turbodocx.com/turbosign/documents/{documentId}/download

Headers

Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Path Parameters

ParameterTypeRequiredDescription
documentIdString (UUID)YesThe unique identifier of the document

Response

{
"downloadUrl": "https://s3.amazonaws.com/bucket/path/to/document.pdf?X-Amz-...",
"fileName": "Signed_Contract_2024.pdf"
}

Response Fields

FieldTypeDescription
downloadUrlStringPresigned S3 URL to download the signed PDF (expires in 1 hour)
fileNameStringOriginal filename of the signed document
Document Status Requirement

This endpoint only returns a download URL when the document status is COMPLETED. If the document is still pending signatures, you will receive an error response.

Usage Notes

  • The presigned URL expires after 1 hour. Request a new URL if the previous one has expired.
  • The downloaded PDF includes all signatures embedded and is legally binding.
  • For large documents, consider streaming the download rather than loading the entire file into memory.

Endpoint 4: Get Audit Trail

Retrieve the complete audit trail for a document, including all events and timestamps. The audit trail provides a tamper-evident record of all actions taken on the document using a cryptographic hash chain.

Endpoint

GET https://api.turbodocx.com/turbosign/documents/{documentId}/audit-trail

Headers

Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Path Parameters

ParameterTypeRequiredDescription
documentIdString (UUID)YesThe unique identifier of the document

Response

{
"data": {
"document": {
"id": "4a20eca5-7944-430c-97d5-fcce4be24296",
"name": "Service Agreement 2024"
},
"auditTrail": [
{
"id": "entry-uuid-1",
"documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
"actionType": "prepared_for_review",
"timestamp": "2024-01-15T10:30:00.000Z",
"previousHash": null,
"currentHash": "a1b2c3d4e5f6...",
"createdOn": "2024-01-15T10:30:00.000Z",
"details": {
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0..."
},
"user": {
"name": "Admin User",
"email": "admin@company.com"
},
"userId": "user-uuid-1"
},
{
"id": "entry-uuid-2",
"documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
"actionType": "document_sent",
"timestamp": "2024-01-15T10:31:00.000Z",
"previousHash": "a1b2c3d4e5f6...",
"currentHash": "b2c3d4e5f6g7...",
"createdOn": "2024-01-15T10:31:00.000Z",
"details": {
"recipientCount": 2
},
"user": {
"name": "Admin User",
"email": "admin@company.com"
},
"userId": "user-uuid-1"
},
{
"id": "entry-uuid-3",
"documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
"actionType": "document_viewed",
"timestamp": "2024-01-15T11:00:00.000Z",
"previousHash": "b2c3d4e5f6g7...",
"currentHash": "c3d4e5f6g7h8...",
"createdOn": "2024-01-15T11:00:00.000Z",
"details": {
"ipAddress": "10.0.0.50"
},
"recipient": {
"name": "John Smith",
"email": "john.smith@company.com"
},
"recipientId": "recipient-uuid-1"
},
{
"id": "entry-uuid-4",
"documentId": "4a20eca5-7944-430c-97d5-fcce4be24296",
"actionType": "document_signed",
"timestamp": "2024-01-15T11:05:00.000Z",
"previousHash": "c3d4e5f6g7h8...",
"currentHash": "d4e5f6g7h8i9...",
"createdOn": "2024-01-15T11:05:00.000Z",
"details": {
"signatureType": "electronic",
"ipAddress": "10.0.0.50"
},
"recipient": {
"name": "John Smith",
"email": "john.smith@company.com"
},
"recipientId": "recipient-uuid-1"
}
]
}
}

Response Fields

Document Object

FieldTypeDescription
idString (UUID)Document identifier
nameStringDocument name

Audit Trail Entry Object

FieldTypeDescription
idString (UUID)Unique identifier for the audit entry
documentIdString (UUID)Document this entry belongs to
actionTypeStringType of action (see Action Types below)
timestampString (ISO)When the action occurred
previousHashStringHash of the previous entry (null for first entry)
currentHashStringHash of this entry (forms hash chain)
createdOnString (ISO)When the entry was created
detailsObjectAdditional action-specific details
userObjectUser who performed action (for sender actions)
userIdString (UUID)User ID (when applicable)
recipientObjectRecipient who performed action (for signer actions)
recipientIdString (UUID)Recipient ID (when applicable)

Action Types

Action TypeDescription
prepared_for_reviewDocument was uploaded and prepared for review
document_sentDocument was sent to recipients for signing
document_viewedRecipient opened/viewed the document
document_signedRecipient signed the document
document_voidedDocument was voided/cancelled
document_resentReminder/resend email was sent to recipient
email_notification_sentSigning invitation email was sent
cc_email_notification_sentCC notification email was sent

Hash Chain Verification

The audit trail uses a cryptographic hash chain for tamper-evidence:

  • Each entry's currentHash is computed from the entry data plus the previousHash
  • The first entry has previousHash: null
  • To verify integrity, recompute each hash and compare
  • Any modification to historical entries would break the chain

This provides strong evidence that the audit trail has not been tampered with after creation.

Usage Notes

  • The audit trail is available at any document status (not just completed documents)
  • All timestamps are in ISO 8601 format (UTC timezone)
  • The details object varies by action type and may contain IP addresses, user agents, and other contextual information
  • Audit trail entries are immutable and cannot be modified or deleted

Endpoint 5: Get Recipients

Retrieve every recipient on a document with their signing status, their email history, and who sent the document. This is the endpoint to poll when you want to know who has signed and who you are still waiting on.

Endpoint

GET https://api.turbodocx.com/turbosign/documents/{documentId}/recipients

Headers

Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Path Parameters

ParameterTypeRequiredDescription
documentIdString (UUID)YesThe unique identifier of the document

Response

{
"data": {
"document": {
"id": "4a20eca5-7944-430c-97d5-fcce4be24296",
"name": "Service Agreement 2024",
"status": "under_review",
"createdOn": "2024-01-15T10:30:00.000Z",
"sentOn": "2024-01-15T10:35:00.000Z",
"expiresAt": null,
"sentBy": {
"name": "Jane Sender",
"email": "jane@acme.com"
}
},
"recipients": [
{
"id": "recipient-uuid-1",
"name": "John Doe",
"email": "john@example.com",
"status": "completed",
"effectiveStatus": "completed",
"signedOn": "2024-01-16T14:02:00.000Z",
"signingOrder": 1,
"delivery": {
"firstSentOn": "2024-01-15T10:35:00.000Z",
"lastSentOn": "2024-01-15T10:35:00.000Z",
"totalSent": 1,
"reminderCount": 0,
"lastRemindedAt": "2024-01-15T10:35:00.000Z",
"warningCount": 0,
"lastWarningAt": null
}
},
{
"id": "recipient-uuid-2",
"name": "Jane Smith",
"email": "jane@example.com",
"status": "pending",
"effectiveStatus": "pending",
"signedOn": null,
"signingOrder": 2,
"delivery": {
"firstSentOn": "2024-01-16T14:02:05.000Z",
"lastSentOn": "2024-01-20T09:00:00.000Z",
"totalSent": 2,
"reminderCount": 1,
"lastRemindedAt": "2024-01-20T09:00:00.000Z",
"warningCount": 0,
"lastWarningAt": null
}
}
],
"summary": {
"total": 2,
"pending": 1,
"viewed": 0,
"completed": 1,
"voided": 0,
"expired": 0,
"waitingOn": 1
}
}
}

Response Fields

document

FieldTypeDescription
statusStringDocument-level status (draft, under_review, completed, voided, expired, …)
sentOnString | nullWhen the document was dispatched to recipients; null while it is still a draft
expiresAtString | nullWhen the signing window closes; null if it never expires
sentByObject{ name, email } — the real sender, never the internal service account

recipients[]

FieldTypeDescription
statusStringRaw status: pending, viewed or completed
effectiveStatusStringpending, viewed, completed, voided or expireduse this for display
signedOnString | nullWhen this recipient signed; null while pending or viewed
signingOrderNumberPosition in the signing sequence
deliveryObjectThis recipient's email history (see below)

recipients[].delivery

FieldTypeDescription
firstSentOnString | nullFirst email of any kind; null if this recipient has never been emailed
lastSentOnString | nullMost recent email of any kind
totalSentNumberSignature request + resends + reminders + expiry warnings + terminal notices
reminderCountNumberAutomatic (scheduled) reminders only — see the note below
lastRemindedAtString | nullWhen the reminder cadence clock was last reset — see the note below
warningCountNumberExpiry warnings sent. Only a warning touches this
lastWarningAtString | nullWhen the last expiry warning went out. Only a warning touches this
reminderCount and lastRemindedAt do not mean what their names suggest

reminderCount counts automatic (scheduled) reminders only. It is the counter that the maxReminders schedule setting caps. A manual "remind now" (the Send Reminder action) is a standalone nudge that deliberately does not consume the cap budget, so it does not increment this — even though the email it sends does appear in totalSent. A recipient can therefore read reminderCount: 0 while reminder emails have genuinely been sent.

lastRemindedAt is a cadence clock, not a record of a reminder. Four things stamp it: the initial signature-request send (so the first reminder is measured from the invitation), each scheduled reminder, each manual "remind now", and each expiry warning (a deliberate cross-reset so a reminder never lands adjacent to a warning). Only the second of those bumps reminderCount.

The common consequence: a freshly-sent document returns a non-null lastRemindedAt equal to the invitation timestamp, alongside reminderCount: 0. That is correct — nobody has been reminded. To answer "have we actually chased this person", read totalSent (all emails) or the audit trail's reminder_sent entries, not reminderCount.

warningCount / lastWarningAt have no such caveat — only an expiry warning touches them.

summary

Counts by effectiveStatus, plus waitingOn — the recipients who have not finished (pending + viewed). waitingOn drops to zero once the document reaches a terminal state.

Two status fields, and they differ on purpose

There is no per-recipient declined, voided or expired state in the system — a recipient row only ever holds pending, viewed or completed. That means on a voided or expired document, an unsigned signer still reads pending in status.

effectiveStatus layers the document's outcome on top, so that same signer reads voided or expired. Branch on effectiveStatus, not status, or you will chase people whose signing links are already dead.

A completed signature is never revoked: someone who signed before the document was voided still reads completed in both fields.

effectiveStatus also reflects a lapsed deadline immediately. A document past its expiresAt keeps its old status until a background sweep updates it, but the signing links are already refused at that moment — so effectiveStatus reports expired right away rather than waiting for the row to change.

Usage Notes

  • Available at any document status, including drafts (a draft returns its recipients with sentOn: null and delivery.totalSent: 0)
  • Recipients are returned ordered by signingOrder
  • delivery counts emails to that signer only — CC notifications are excluded, since a CC address is not a signer
  • On a document with a signing order, only the current turn has been emailed; delivery.totalSent === 0 distinguishes "not yet invited" from "invited and not acted"
  • Returns 404 if the document does not exist or belongs to another organization
  • All timestamps are ISO 8601 (UTC)

Endpoint 6: Resend Email

Resend signature request emails to one or more recipients who haven't yet completed signing. Only recipients at the current signing order who haven't completed are eligible for resend.

This applies to documents that have already been sent for signing — there must be an original invitation to resend. A document still in review (no signing emails sent yet) has no eligible recipients.

Endpoint

POST https://api.turbodocx.com/turbosign/documents/{documentId}/resend-email

Headers

Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Path Parameters

ParameterTypeRequiredDescription
documentIdString (UUID)YesThe unique identifier of the document

Request Body (JSON)

FieldTypeRequiredDescription
recipientIdsArray (UUID[])YesArray of recipient UUIDs to resend emails to (min 1, unique)
{
"recipientIds": [
"5f673f37-9912-4e72-85aa-8f3649760f6b",
"7a891c23-4d56-4e78-9abc-def012345678"
]
}
Where do I get recipient IDs?

Recipient IDs are returned in the response of the Prepare for Review (Endpoint 1) and Prepare for Signing (Endpoint 2) endpoints. Save these IDs when creating your signature request.

Response

{
"data": {
"success": true,
"recipientCount": 2
}
}

Response Fields

FieldTypeDescription
data.successBooleanWhether the resend was successful
data.recipientCountNumberNumber of recipients who received the email

Error Responses

StatusError MessageCause
404"Document not found"Document doesn't exist, has been deleted, or belongs to another organization
400"All recipients have already completed signing"No eligible recipients remain
400"Some recipients are not eligible for email resend at this time"Requested recipients aren't at the current signing order or already completed

The 400 "not eligible" error also returns an invalidRecipientIds array showing which IDs were rejected:

{
"error": "Some recipients are not eligible for email resend at this time",
"invalidRecipientIds": ["7a891c23-4d56-4e78-9abc-def012345678"]
}

To recover, retry the request without the listed IDs, or wait until those recipients reach the current signing order before resending to them.

Usage Notes

  • Only recipients at the current signing order who haven't completed signing are eligible for resend. If your document uses sequential signing (signingOrder 1, 2, 3...), that means the current active step only.
  • The request is all-or-nothing: if any requested ID is ineligible, the whole request is rejected with a 400 and no emails are sent. Fix the recipientIds and retry.
  • Each resend creates a document_resent entry in the audit trail for tracking
  • The recipientIds array must contain at least one ID and all IDs must be unique UUIDs

Endpoint 7: Send Reminder

Send a reminder email to a document's outstanding signers.

This is a standalone nudge, deliberately decoupled from the automatic reminder schedule: it ignores the configured cadence, works even when reminders are disabled or the per-signer cap is already spent, and does not consume that cap. Use it when someone asks you to "chase them again" outside the normal rhythm.

Distinct from Resend Email: resend re-sends the original invitation; remind sends the reminder copy.

Endpoint

POST https://api.turbodocx.com/turbosign/documents/{documentId}/send-reminder

Headers

Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
x-rapiddocx-org-id: YOUR_ORGANIZATION_ID
User-Agent: TurboDocx API Client

Path Parameters

ParameterTypeRequiredDescription
documentIdString (UUID)YesThe unique identifier of the document

Request Body (JSON)

FieldTypeRequiredDescription
recipientIdsArray (UUID[])NoSubset to remind. Omit to remind every eligible signer. Min 1, unique

Omit the body entirely (or send {}) to remind everyone whose turn it is:

{}

Or name specific signers:

{
"recipientIds": ["5f673f37-9912-4e72-85aa-8f3649760f6b"]
}
Don't send an empty array

recipientIds must contain at least one ID when the key is present. Sending {"recipientIds": []} is rejected with a 400. To remind everyone, omit the key.

Response

{
"data": {
"results": [
{ "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", "status": "sent", "reminderCount": 2, "phase": "reminder" },
{ "recipientId": "7a891c23-4d56-4e78-9abc-def012345678", "status": "skipped_wrong_order" }
]
}
}

Response Fields

FieldTypeDescription
data.resultsArrayOne entry per recipient considered — including those skipped
results[].recipientIdStringThe recipient this outcome refers to
results[].statusStringWhat happened — see the table below
results[].reminderCountNumberReminder count after the send. Only meaningful when sent
results[].phaseStringreminder or expiring — which email copy was used

A recipient who was not emailed is reported rather than silently dropped, so you can tell nobody received anything:

statusMeaning
sentThe reminder email was sent
failedThe send was attempted and the mail transport rejected it
skipped_completedThat recipient has already signed
skipped_wrong_orderThey're at a later signing order — it isn't their turn yet
skipped_not_dueScheduled path only: their next reminder isn't due
skipped_max_reachedScheduled path only: their reminder cap is spent
skipped_disabledScheduled path only: reminders are off for this document
skipped_claim_lostA concurrent request won the send; no duplicate was sent

Error Responses

StatusError MessageCause
404"Document not found"Document doesn't exist, has been deleted, or belongs to another organization
409"This document has expired, so its signers can no longer be reminded."The signing window has closed — the link is dead, so a reminder would be useless
409"This document has been voided, so its signers can no longer be reminded."The document was voided
400"Some recipients are not eligible for a reminder at this time"A named recipient isn't a current-order pending signer

The 409 responses carry code: "DocumentNotActionable" plus the document's status, so a client can refresh a stale row rather than guessing.

The 400 returns an invalidRecipientIds array showing which IDs were rejected. The request is all-or-nothing — if any named ID is ineligible, no emails are sent:

{
"error": "Some recipients are not eligible for a reminder at this time",
"invalidRecipientIds": ["7a891c23-4d56-4e78-9abc-def012345678"]
}

Usage Notes

  • Only recipients at the current signing order who haven't completed are eligible.
  • Unlike the automatic schedule, this is not gated by remindersEnabled or maxReminders, and it does not increment the reminder count — so it works on a document that never turned reminders on, or one whose cap is already spent.
  • Each send creates a reminder_sent entry in the audit trail, recorded against the user who triggered it.

Reminders & Expiration

Two independent, opt-in features you can configure organization-wide in E-Signature Settings → Reminders & Expiration, or per-document via the schedule fields on either send endpoint.

Both ship off by default. A document sent without any schedule fields behaves exactly as it always has: no reminders, no expiry.

The schedule

FieldDefaultMeaning
remindersEnabledfalseSend reminder emails at all
reminderDelay3 daysTime to the first reminder, measured from that signer's invitation
reminderInterval3 daysGap between subsequent reminders
maxReminders5Cap per signer, max 50. -1 unlimited, 0 none. Never caps expiry warnings
expirationEnabledfalseExpire the document at all
expireAfter120 daysHow long the document stays signable, counted from sending
expirationWarning3 daysHow far before expiry warnings start. 0 = never warn
expirationWarningInterval1 dayGap between warnings once they start

reminderDelay and reminderInterval are separate because a single interval can't express "first at 3 days, then every 5". expirationWarning / expirationWarningInterval mirror that split exactly.

Two independent tracks

Reminders and expiry warnings run as two separate clocks, so a signer gets the full escalating stream — reminders keep coming even after the warning window opens. The two are coordinated so they never collide: on any given tick a due warning takes priority and restarts the reminder clock, so a signer never receives a reminder and a warning at the same moment.

Expiry warnings also still fire when reminders are off or capped — the deadline notice belongs to expiration, not to reminders.

Expiry

When expirationEnabled is on, the document carries an absolute deadline:

  • expiresAt — an ISO timestamp on the document, absent when it has no deadline.
  • Once that instant passes, every signing link returns HTTP 410 with code: "DocumentExpired", and the document moves to the terminal status expired.
  • Expiry is enforced in real time on every signing request, so a link stops working exactly on time regardless of background processing.

A document that was already completed keeps working — the deadline exists to stop signing, and signing is already finished.

Validation

The API rejects contradictory combinations with 400 and error: "InvalidSignatureSchedule", plus a machine-readable data.code and the conflicting data.fields:

CodeRule
ExpiryWindowTooShortexpireAfter must be at least one hour
WarningExceedsExpiryexpirationWarning must be less than expireAfter
WarningIntervalTooShortexpirationWarningInterval must be at least one hour when warnings are on
ReminderDelayExceedsExpiryThe first reminder must fall inside the expiry window, or it would never send
ReminderIntervalExceedsExpiryFollow-up reminders must fit inside the window

The canonical mistake these prevent: "remind every 4 days" with "expire in 2 days" — the document would die before the first nudge was ever due, and you'd believe reminders were on while no email ever sent.


Recipients Reference

Recipient Properties

Each recipient object in the recipients array should contain the following properties:

PropertyTypeRequiredDescription
nameStringYesFull name of the recipient/signer
emailString (email)YesEmail address of the recipient (must be unique)
signingOrderNumberYesOrder in which recipient should sign (starts at 1)
metadataObjectNoOptional metadata for UI customization (color, lightColor)

Metadata Object (Optional)

The metadata object allows you to customize the recipient's UI appearance:

PropertyTypeDescriptionExample
colorStringPrimary color for recipient in HSL format"hsl(200, 75%, 50%)"
lightColorStringLight background color for recipient in HSL format"hsl(200, 75%, 93%)"

Example Recipients Array

[
{
"name": "John Smith",
"email": "john.smith@company.com",
"signingOrder": 1
},
{
"name": "Jane Doe",
"email": "jane.doe@partner.com",
"signingOrder": 2
}
]

With Optional Metadata

[
{
"name": "John Smith",
"email": "john.smith@company.com",
"signingOrder": 1,
"metadata": {
"color": "hsl(200, 75%, 50%)",
"lightColor": "hsl(200, 75%, 93%)"
}
}
]

Field Types Reference

Complete Field Type List

TypeDescriptionAuto-filledUse Case
signatureElectronic signature fieldNoLegal signatures, agreements
initialInitial fieldNoDocument initials, paragraph acknowledgments
dateDate picker fieldNoSigning date, agreement date
full_nameFull name fieldYesAutomatically fills signer's complete name
first_nameFirst name fieldYesAutomatically fills signer's first name
last_nameLast name fieldYesAutomatically fills signer's last name
titleTitle/job title fieldNoProfessional title or position
companyCompany name fieldNoOrganization or company name
emailEmail address fieldYesSigner's email address
textGeneric text input fieldNoCustom text, notes, or any other text input
checkboxCheckbox fieldNoAcknowledgments, consent, agreements

Field Configuration Properties

Common Properties (All Field Types)

PropertyTypeRequiredDescription
recipientEmailStringYesEmail address of recipient (matches email in recipients array)
typeStringYesField type (see table above)
requiredBooleanNoWhether field must be completed (default: true)
defaultValueStringNoPre-filled value for the field (max 600 characters)
isReadonlyBooleanNoMakes field non-editable (for prefilled values)
backgroundColorStringNoCustom background color (hex or rgba)
metadataObjectNoOptional field metadata. Carries fieldKey (on a controlling checkbox) and/or a conditional rule (on a dependent field). See Conditional (IF/THEN) Fields.

Template-based Properties

PropertyTypeRequiredDescription
template.anchorStringYesText anchor to find in document (e.g., {Signature1})
template.placementStringYesHow to place field: "replace", "before", "after"
template.sizeObjectYesField dimensions: { width: number, height: number }
template.offsetObjectNoPosition offset: { x: number, y: number } (default: {x:0, y:0})
template.caseSensitiveBooleanNoWhether anchor search is case-sensitive (default: true)
template.useRegexBooleanNoWhether to treat anchor as regex pattern (default: false)

Coordinate-based Properties

PropertyTypeRequiredDescription
pageNumberYesPage number (starts at 1)
xNumberYesHorizontal position from left edge (pixels)
yNumberYesVertical position from top edge (pixels)
widthNumberYesField width in pixels
heightNumberYesField height in pixels
pageWidthNumberNoTotal page width in pixels (optional, for responsive positioning)
pageHeightNumberNoTotal page height in pixels (optional, for responsive positioning)

Conditional (IF/THEN) Fields

Any field can carry an optional metadata object. It is used to build conditional (IF/THEN) relationships between fields: a controlling checkbox decides whether one or more dependent fields are shown or unlocked. This works with both the prepare-for-signing and prepare-for-review single-step routes (and with the Bulk API, which uses the same field format).

The relationship has two halves:

  1. The controlling checkbox gets a stable identifier via metadata.fieldKey. It must be a field of type: "checkbox".
  2. Each dependent field points back at that checkbox with a metadata.conditional rule whose controllingFieldKey equals the checkbox's fieldKey.

metadata Object

PropertyTypeRequiredDescription
fieldKeyStringNoStable identifier for a controlling checkbox. Referenced by dependent fields' controllingFieldKey. Set this on the checkbox (type: "checkbox").
conditionalObjectNoRule placed on a dependent field that makes its visibility/editability depend on a controlling checkbox. See below.

metadata.conditional Object

PropertyTypeRequiredDescription
controllingFieldKeyStringYesThe metadata.fieldKey of the controlling checkbox this field depends on. Must be non-empty.
operatorStringYesCondition to evaluate against the checkbox: "is_checked" or "is_not_checked".
actionStringYesWhat happens to this field when the condition is met: "show" or "unlock".

show vs. unlock

The action controls the dependent field's starting state and what the condition changes:

  • show — the dependent field is hidden until the condition is met. When the checkbox matches the operator, the field appears.
  • unlock — the dependent field is visible but read-only until the condition is met. When the checkbox matches the operator, the field becomes editable.

Worked Example

A recipient checks "Request changes" and a text box appears asking them to explain. The checkbox is the controller (fieldKey: "request_changes"); the text field shows only when the box is checked.

const fields = JSON.stringify([
// Controlling checkbox — gets a stable fieldKey
{
recipientEmail: "john.smith@company.com",
type: "checkbox",
page: 1,
x: 100,
y: 400,
width: 20,
height: 20,
required: false,
metadata: {
fieldKey: "request_changes",
},
},
// Dependent text field — hidden until the checkbox above is checked
{
recipientEmail: "john.smith@company.com",
type: "text",
page: 1,
x: 130,
y: 400,
width: 300,
height: 60,
required: false,
defaultValue: "",
metadata: {
conditional: {
controllingFieldKey: "request_changes",
operator: "is_checked",
action: "show",
},
},
},
]);
formData.append("fields", fields);

To instead keep the field visible but locked until the box is checked, change the dependent field's action to "unlock". To reveal a field when a box is cleared (for example, an "I do not consent — explain why" note), use operator: "is_not_checked".

Validation and Fail-Open Behavior

The API validates the shape of every conditional rule. A malformed rule is rejected with HTTP 400 and the type InvalidConditionalRule. A rule is malformed when:

  • operator is anything other than "is_checked" or "is_not_checked", or
  • action is anything other than "show" or "unlock", or
  • controllingFieldKey is missing or empty.
{
"message": "Field 2: metadata.conditional.operator must be one of: is_checked, is_not_checked",
"type": "InvalidConditionalRule"
}

A well-formed rule whose controllingFieldKey does not match any checkbox's fieldKey in the same request does not error — it fails open. The dependent field stays visible and editable as if it had no rule, so a typo in controllingFieldKey silently disables the condition rather than blocking the send. Double-check that the controllingFieldKey on each dependent field exactly matches the fieldKey of an existing checkbox.

Field Type Special Behaviors

signature & initial

  • Draws a signature pad for user input
  • Can be text-based or drawn
  • Cryptographically signed and hashed for legal validity
  • Cannot carry a defaultValue — sending one is rejected

date

  • Shows date picker interface
  • Format: MM/DD/YYYY (US) or DD/MM/YYYY (configurable)
  • Fills automatically with the date the recipient signs
  • To pin a specific date instead, set defaultValue to that date in MM/DD/YYYY format (e.g. "12/31/2026"). Omit defaultValue (or send "") to keep the signing-date behavior
  • defaultValue must be a real calendar date in MM/DD/YYYY — a non-existent date such as "02/31/2026" (or any malformed value) is rejected with 400 InvalidDateValue. There is no "today" keyword
  • A date field cannot be isReadonly — a pinned date still shows to the signer, it is not locked

full_name, first_name, last_name, email

  • Auto-populated from recipient profile
  • Can be overridden by recipient if needed
  • Useful for legal compliance and form filling

text

  • Single-line text input by default
  • Supports defaultValue for prefilled content
  • Use for titles, company names, custom fields

checkbox

  • Boolean true/false value
  • Useful for acknowledgments and consent
  • Can have label text next to checkbox

Field Positioning Methods

TurboSign supports two methods for positioning signature fields in your documents.

Uses text anchors in your PDF as placeholders. TurboSign searches for these anchors and places fields accordingly.

Advantages

✅ Easy to update field positions (just edit the PDF) ✅ No need to measure exact coordinates ✅ Works across different page sizes ✅ More maintainable for non-technical users ✅ Handles document variations gracefully

How it Works

  1. Add anchor text to your PDF: Place text like {Signature1}, {Date1}, {Initial1} where you want fields
  2. Configure fields with anchor references: Tell TurboSign what to search for
  3. TurboSign finds and replaces: Anchors are found and replaced with interactive fields

Anchor Configuration Example

{
"recipientEmail": "john.smith@company.com",
"type": "signature",
"template": {
"anchor": "{Signature1}",
"placement": "replace",
"size": { "width": 200, "height": 80 },
"offset": { "x": 0, "y": 0 },
"caseSensitive": true,
"useRegex": false
},
"required": true
}

Placement Options

  • replace: Removes the anchor text and places the field in its position
  • before: Places field before the anchor text (anchor remains visible)
  • after: Places field after the anchor text (anchor remains visible)

Offset Usage

Offset allows fine-tuning field position relative to the anchor:

  • x: Positive moves right, negative moves left (pixels)
  • y: Positive moves down, negative moves up (pixels)
{
"anchor": "{Signature1}",
"placement": "replace",
"size": { "width": 200, "height": 80 },
"offset": { "x": 10, "y": -5 } // 10px right, 5px up from anchor
}

Method 2: Coordinate-based Positioning

Uses exact pixel coordinates to position fields on specific pages. Best for precise control or when anchors aren't feasible.

Advantages

✅ Pixel-perfect precision ✅ Works with PDFs that can't be edited ✅ Programmatically generated positions ✅ Useful for form-filling scenarios ✅ Consistent placement across documents

How it Works

  1. Measure exact x,y coordinates in your PDF (using PDF editor or viewer)
  2. Provide page number, coordinates, and dimensions
  3. TurboSign places fields at exact positions

Coordinate Configuration Example

{
"recipientEmail": "john.smith@company.com",
"type": "signature",
"page": 1,
"x": 100,
"y": 200,
"width": 200,
"height": 80,
"pageWidth": 612,
"pageHeight": 792,
"required": true
}

Coordinate System Reference

  • Origin (0,0): Top-left corner of the page
  • X-axis: Increases from left to right
  • Y-axis: Increases from top to bottom
  • Standard US Letter: 612 x 792 pixels (8.5" x 11" at 72 DPI)
  • Standard A4: 595 x 842 pixels (210mm x 297mm at 72 DPI)

Coordinate Validation

Fields must stay within page boundaries:

  • x ≥ 0
  • y ≥ 0
  • x + width ≤ pageWidth
  • y + height ≤ pageHeight

Measuring Coordinates

Adobe Acrobat Pro:

  1. View → Show/Hide → Rulers & Grids → Rulers
  2. Hover over location to see coordinates

Browser Developer Tools:

  1. Open PDF in browser
  2. Right-click → Inspect
  3. Use element inspector to measure positions

PDF Editing Software:

  • Use built-in coordinate display
  • Draw rectangles to measure dimensions

Quick Coordinate Example

Position a signature field at bottom-right of a US Letter page:

{
"recipientEmail": "john@example.com",
"type": "signature",
"page": 1,
"x": 362, // 612 - 250 = 362 (right aligned with 50px margin)
"y": 662, // 792 - 130 = 662 (bottom aligned with 50px margin)
"width": 200,
"height": 80,
"pageWidth": 612,
"pageHeight": 792
}

Best Practices

Workflow Selection

When You Need Field Verification:

  • ✅ Use prepare-for-review to get preview URLs
  • ✅ Verify field placement in browser before sending
  • ✅ Manually trigger sending after review
  • ✅ Useful for new document templates or complex field layouts

When Field Placement Is Verified:

  • ✅ Use prepare-for-signing to send immediately
  • ✅ Implement webhook handlers for completion notifications
  • ✅ Use proper error handling and retry logic
  • ✅ Monitor API rate limits
  • ✅ Log all document IDs for tracking

General Tips:

  • ✅ Use deliverableId or templateId to avoid repeated uploads
  • ✅ Test with your own email addresses first
  • ✅ Both endpoints are production-ready

Security

  • Never expose API tokens: Store tokens securely in environment variables or secrets management
  • Use HTTPS only: All API calls must use HTTPS in production (API enforces this)
  • Validate inputs: Always validate recipient emails and document names before submission
  • Implement rate limiting: Respect API rate limits to avoid throttling
  • Rotate tokens regularly: Generate new API tokens periodically
  • Use webhook signatures: Verify webhook payloads using HMAC signatures
  • Sanitize user inputs: Validate and sanitize all user-provided data

Error Handling

  • Check HTTP status codes: Always verify response status before processing
  • Handle timeouts: Implement retry logic with exponential backoff for network failures
  • Log API responses: Keep detailed logs for debugging and monitoring
  • Validate responses: Check response structure before accessing data
  • Graceful degradation: Have fallback behavior for API failures
  • User-friendly errors: Display helpful error messages to end users

Performance

File Upload Optimization:

  • Compress PDFs when possible (aim for <5MB)
  • Use fileLink for files already in cloud storage (S3, GCS, etc.)
  • Use deliverableId/templateId to reference existing documents
  • Avoid uploading the same document multiple times

API Efficiency:

  • Single-step endpoints reduce API calls from 3 to 1 (3x faster)
  • Batch multiple documents in parallel requests when possible
  • Use connection pooling for multiple requests
  • Implement exponential backoff for retries
  • Cache responses when appropriate

Network Optimization:

  • Use CDN for document hosting when using fileLink
  • Enable gzip compression for API requests
  • Minimize payload sizes by only including required fields

Document Preparation

Text Anchors (Template-based):

  • Use consistent anchor naming: {FieldType}{Number} (e.g., {Signature1}, {Date1})
  • Place anchors exactly where you want fields
  • Use unique anchors (avoid duplicates)
  • Test anchor placement with prepare-for-review first
  • Document your anchor naming convention

Coordinate-based:

  • Verify coordinates work across different PDF viewers
  • Account for page margins and headers/footers
  • Use standard page sizes when possible
  • Test on actual page dimensions (don't assume)
  • Validate boundaries before submission

Document Validation:

  • Ensure PDFs are not password-protected or corrupted
  • Verify all pages are readable
  • Test with actual documents before production
  • Keep backup copies of source documents

JSON String Formatting

⚠️ Critical: Recipients and fields must be valid JSON strings when added to form-data.

Correct:

const recipients = JSON.stringify([
{ name: "John", email: "john@example.com", signingOrder: 1 },
]);
formData.append("recipients", recipients);

Incorrect:

// Don't send object/array directly!
formData.append("recipients", recipientsArray); // ❌ Wrong
formData.append("recipients", "[{...}]"); // ❌ Wrong (string literal, not stringified)

Python Example:

import json
recipients = json.dumps([
{"name": "John", "email": "john@example.com", "signingOrder": 1}
])
form_data['recipients'] = recipients

C# Example:

using System.Text.Json;
var recipients = JsonSerializer.Serialize(new[] {
new { name = "John", email = "john@example.com", signingOrder = 1 }
});
formData.Add(new StringContent(recipients), "recipients");

Error Handling & Troubleshooting

Common HTTP Status Codes

Status CodeDescriptionSolution
200SuccessRequest completed successfully
400Bad RequestCheck request body format and required fields
401UnauthorizedVerify API token and headers
403ForbiddenCheck organization ID and permissions
404Not FoundVerify endpoint URLs are correct
422Unprocessable EntityValidate field values and constraints
429Too Many RequestsImplement rate limiting and retry logic
500Internal Server ErrorContact support if persistent

Common Issues

JSON String Formatting Errors

Symptoms: 400 Bad Request with message "Invalid JSON in recipients/fields"

Solutions:

  • ✅ Verify JSON.stringify() or equivalent is used for recipients, fields, ccEmails
  • ✅ Check JSON is valid using JSONLint or similar validator
  • ✅ Ensure proper escaping of quotes in JSON strings
  • ✅ Test with minimal example first (1 recipient, 1 field)

Example Error Response:

{
"error": "Invalid JSON string in recipients field",
"code": "JSONParseError",
"details": "Unexpected token at position 45"
}

Debug Steps:

  1. Log the JSON string before sending
  2. Validate JSON with online validator
  3. Check for special characters or unescaped quotes
  4. Test with hardcoded valid JSON first

File Source Errors

Symptoms: 400 Bad Request with message about file source

Solutions:

  • ✅ Provide exactly ONE of: file, deliverableId, templateId, fileId, fileLink
  • ✅ Verify UUIDs are valid format (8-4-4-4-12 characters)
  • ✅ Check file upload isn't corrupted or empty
  • ✅ Ensure fileLink is accessible (not behind auth)

Example Error Response:

{
"error": "Must provide exactly one file source",
"code": "InvalidFileSource"
}

Recipients/Fields Mismatch

Symptoms: 400 Bad Request about missing recipient or email mismatch

Solutions:

  • ✅ Verify recipientEmail in fields matches email in recipients array exactly
  • ✅ Check for typos in email addresses
  • ✅ Ensure all fields reference valid recipients
  • ✅ Email matching is case-sensitive

Example:

// Recipients array
[{ email: "john.smith@company.com", ... }]

// Fields array - must match exactly
[{ recipientEmail: "john.smith@company.com", ... }] // ✅ Correct
[{ recipientEmail: "John.Smith@company.com", ... }] // ❌ Wrong (case mismatch)

Authentication Failures

Symptoms: 401 Unauthorized responses

Solutions:

  • ✅ Verify API token is correct and not expired
  • ✅ Check that x-rapiddocx-org-id header matches your organization
  • ✅ Ensure Bearer token format: Bearer YOUR_TOKEN (with space)
  • ✅ Confirm token has necessary permissions

Example Correct Headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
x-rapiddocx-org-id: a1b2c3d4-e5f6-7890-abcd-ef1234567890

Document Upload Failures

Symptoms: Upload returns error or times out

Solutions:

  • ✅ Verify PDF file is not corrupted or password-protected
  • ✅ Check file size is under maximum limit (typically 10MB)
  • ✅ Ensure file is actually a PDF (check MIME type)
  • ✅ Verify network connection and try again
  • ✅ For fileLink, ensure URL is accessible

Field Positioning Problems

Symptoms: Signature fields appear in wrong locations or not at all

Template-based Solutions:

  • ✅ Verify anchor text exists in the PDF document
  • ✅ Check anchor text matches exactly (case-sensitive by default)
  • ✅ Test with caseSensitive: false if having matching issues
  • ✅ Try different placement options (replace, before, after)
  • ✅ Use prepare-for-review to visually verify placement

Coordinate-based Solutions:

  • ✅ Verify page dimensions match your PDF's actual size
  • ✅ Check that x,y coordinates are within page boundaries
  • ✅ Ensure coordinates account for any PDF margins or headers
  • ✅ Test with different page numbers if multi-page document
  • ✅ Validate that x + width ≤ pageWidth and y + height ≤ pageHeight

Webhook Integration Issues

Symptoms: Not receiving completion notifications

Solutions:

  • ✅ Verify webhook URLs are accessible and return 200 OK
  • ✅ Check webhook configuration in organization settings
  • ✅ Review webhook delivery history for error details
  • ✅ Test webhook endpoints with external tools (webhook.site, ngrok)
  • ✅ Implement HMAC signature verification

Debugging Tips

  1. Test with prepare-for-review first: Visual confirmation before sending emails
  2. Use preview URLs: Verify field placement and document appearance
  3. Check response documentId: Save this for tracking and debugging
  4. Enable request logging: Log all requests and responses with timestamps
  5. Test with minimal payloads: Start simple (1 recipient, 1 field), add complexity incrementally
  6. Validate JSON before sending: Use JSON validators to check format
  7. Use Postman/Insomnia: Test manually before writing code
  8. Check API status page: Verify TurboDocx services are operational
  9. Review error messages carefully: Error responses include specific details
  10. Monitor rate limits: Track API usage to avoid throttling

Example Debug Request

# Test with curl to isolate issues
curl -X POST https://api.turbodocx.com/turbosign/single/prepare-for-review \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "x-rapiddocx-org-id: YOUR_ORG_ID" \
-F "file=@document.pdf" \
-F "documentName=Test Document" \
-F "senderEmail=you@yourcompany.com" \
-F 'recipients=[{"name":"Test User","email":"test@example.com","signingOrder":1}]' \
-F 'fields=[{"recipientEmail":"test@example.com","type":"signature","page":1,"x":100,"y":200,"width":200,"height":80,"pageWidth":612,"pageHeight":792}]' \
-v

Next Steps

Webhooks - The Next Logical Step

Now that you've integrated the single-step signing flow, the next step is setting up webhooks to receive real-time notifications when documents are signed. This eliminates the need for polling and provides instant updates about document status changes.

📖 Learn how to configure Webhooks →

Support

Need help with your integration?


Ready to get started? Follow the guide above to integrate TurboSign single-step API into your application and start collecting electronic signatures programmatically with a single API call!